package zl0.marshalling;

import java.lang.reflect.Array;
import java.lang.reflect.Method;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Set;

public class Marshal implements MarshallingConstants {

    public static String marshal(Object obj) throws Exception {
    	final StringBuilder sb = new StringBuilder();
    	final ArrayList<Class<?>> clases = new ArrayList<Class<?>>();
    	final ArrayList<String> atributos = new ArrayList<String>();    	
        marshal(obj, sb, clases, atributos, -1);
        final StringBuilder sb2 = new StringBuilder("<").append(Elemento.root).append("><").append(Elemento.clases).append(">");
        for(Class<?> clase : clases) {
        	sb2.append("<").append(Elemento.clase).append(" ").append(Atributo.index).append("=\"").append(getIndex(clases, clase)).append("\"").append(Atributo.name).append("=\"").append(clase.isArray() ? clase.getCanonicalName() : clase.getName()).append("\"/>");
        }
        sb2.append("</").append(Elemento.clases).append("><").append(Elemento.campos).append(">");
		for(String atributo : atributos) {
			sb2.append("<").append(Elemento.campo).append(" ").append(Atributo.index).append("=\"").append(getIndex(atributos, atributo)).append("\"").append(Atributo.name).append("=\"").append(atributo).append("\"/>");
        }
		return sb2.append("</").append(Elemento.campos).append(">").append(sb).append("</").append(Elemento.root).append(">").toString();
    }

    private static StringBuilder marshal(Object obj, StringBuilder sb, List<Class<?>> clases, List<String> atributos, int index) throws Exception {
        if(obj == null) {
            return null;
        }
        final Class<?> clasz = obj.getClass();
        addToList(clases, clasz);
        if(obj instanceof String || obj instanceof Byte || obj instanceof Short || obj instanceof Integer || obj instanceof Long || obj instanceof Float || obj instanceof Character || obj instanceof Boolean || obj instanceof BigDecimal || obj instanceof Date) {
        	sb.append("<").append(Elemento.objeto).append(" ").append(Atributo.type).append("=\"").append(getIndex(clases, clasz)).append('"');
			if(index != -1) {
				sb.append(Atributo.index).append("=\"").append(index).append('"');
			}
			if(obj instanceof String) {
				if("".equals(obj)) {
					return sb.append("></").append(Elemento.objeto).append(">");
				}
				return sb.append("><![CDATA[").append(obj).append("]]></").append(Elemento.objeto).append(">");
			}
			return sb.append(Atributo.value).append("=\"").append(obj instanceof Date ? ((Date) obj).getTime() : (obj instanceof Boolean ? MarshallingUtils.getCompressedBoolean((Boolean) obj) : obj)).append("\"/>");
        }
        if(clasz.isArray()) {
        	addToList(clases, clasz.getComponentType());
            sb.append("<").append(Elemento.array).append(" ").append(Atributo.lenght).append("=\"").append(Array.getLength(obj)).append("\"").append(Atributo.type).append("=\"").append(getIndex(clases, clasz));
            if(index != -1) {
				sb.append("\"").append(Atributo.index).append("=\"").append(index);
			}
            sb.append("\">");
            for(int i = 0; i < Array.getLength(obj); i++) {
            	marshal(Array.get(obj, i), sb, clases, atributos, i);
            }
            return sb.append("</").append(Elemento.array).append(">");
        }
    	sb.append("<o ").append(Atributo.type).append("=\"").append(getIndex(clases, clasz)).append('"');
        if(obj instanceof Collection) {
            sb.append(Atributo.lenght).append("=\"").append(((Collection<?>)obj).size()).append('"');
        }
        if(obj instanceof Map) {
            sb.append(Atributo.lenght).append("=\"").append(((Map<?, ?>)obj).size()).append('"');
        }
        if(index != -1) {
            sb.append(Atributo.index).append("=\"").append(index).append('"');
        }
        sb.append('>');
        if(obj instanceof Collection) {
        	int i = 0;
            for(Object obj2 : ((Collection<?>)obj)) {
            	marshal(obj2, sb, clases, atributos, obj instanceof Set ? -1 : i++);
            }
            return sb.append("</").append(Elemento.objeto).append(">");
        }
        if(obj instanceof Map) {
        	final Map<?, ?> map = ((Map<?, ?>)obj);
        	int i = 0;
        	for(Object obj2 : map.keySet()) {
        		marshal(obj2, sb.append("<").append(Elemento.key).append(" ").append(Atributo.index).append("=\"").append(i).append("\">"), clases, atributos, index).append("</k>");
            	marshal(map.get(obj2), sb.append("<").append(Elemento.value).append(" ").append(Atributo.index).append("=\"").append(i++).append("\">"), clases, atributos, index).append("</v>");
            }
            return sb.append("</").append(Elemento.objeto).append(">");
        }
        for(Method method : clasz.getMethods()) {
            final String nombreMetodo = method.getName();
            if(nombreMetodo.startsWith("get") && !"getClass".equals(nombreMetodo)) {
                final Object valor = method.invoke(obj);
                if(valor != null) {
                	final String campo = nombreMetodo.replaceFirst("get", "");
                	addToList(atributos, campo);
                    sb.append("<").append(Elemento.set).append(" ").append(Atributo.atributo).append("=\"").append(getIndex(atributos, campo)).append('"');
                    final Class<?> clasz2 = valor.getClass();
                    if(clasz2.isArray() || valor instanceof Collection || !(valor instanceof Byte || valor instanceof Short || valor instanceof Integer || valor instanceof Long || valor instanceof Float || valor instanceof Character || valor instanceof Boolean || valor instanceof BigDecimal || valor instanceof Date)) {
                    	marshal(valor, sb.append('>'), clases, atributos, index).append("</").append(Elemento.set).append(">");
                    } else {
                    	addToList(clases, clasz2);
                    	sb.append(Atributo.type).append("=\"").append(getIndex(clases, clasz2)).append("\"").append(Atributo.value).append("=\"").append(valor instanceof Date ? ((Date) valor).getTime() : (valor instanceof Boolean ? MarshallingUtils.getCompressedBoolean((Boolean) valor) : valor)).append("\"/>");
                    }
                }
            }
        }
        return sb.append("</").append(Elemento.objeto).append(">");
    }

    private static <T> void addToList(List<T> lista, T obj) {
        if(!lista.contains(obj)) {
        	lista.add(obj);
        }
    }

    private static <T> String getIndex(List<T> lista, T obj) {
        final int i = lista.indexOf(obj);
        return MarshallingUtils.getCompressedNumber((long)i);
    }
}